<?php

use pmvc\Application;
use pmvc\Controller;
use pmvc\RequestWrapper;
use pmvc\ViewHandler;
use pmvc\ObjectConfiguration;

/**
 * Globals
 */
global $PMVC_LOG_FILEPATH;
$PMVC_LOG_FILEPATH = FALSE;

global $PMVC_LOG_LEVEL;
$PMVC_LOG_LEVEL = "DEBUG";

global $PMVC_CLASSPATH;
$PMVC_CLASSPATH = Array();

global $PMVC_PROPERTIES;
$PMVC_PROPERTIES = Array();

global $PMVC_VIEW_ONLY_CONTROLLER;
$PMVC_VIEW_ONLY_CONTROLLER = "pmvc\controller\ViewOnlyController";

global $PMVC_APPLICATION;

/**
 * The entry point for the entire PMVC system.
 * @param $configuration the configuration file
 * @return string configuration file
 */
function pmvc_start($configuration) {
	global $PMVC_CLASSPATH;
	global $PMVC_PROPERTIES;
	global $PMVC_LOG_FILEPATH;
	global $PMVC_LOG_LEVEL;
	global $PMVC_APPLICATION;
	
	// start the session
	session_start();
	
	// avoid some annoying warnings
	if (pmvc_empty(ini_get("date.timezone"))) {
		date_default_timezone_set('America/Los_Angeles');
	}
	
	// check the existance of the file
	if (!file_exists($configuration)) {
		die(pmvc_pstring(
			"Configuration file doesn't exist: {0}", $configuration));
	}
	
	// load the configuration file
	$xml = simplexml_load_file($configuration);
	if ($xml===FALSE) {
		die(pmvc_pstring(
			"Unable to parse configuration file: {0}", $configuration));
	}
	
	// load properties
	foreach ($xml->xpath("/pmvc-application/properties/*") as $prop) {
		if ($prop->getName()=="file") {
			$properties = pmvc_load_properties(
				pmvc_property_string((string)$prop["path"]),
				pmvc_property_string(strtolower($prop["dieonerror"]))==="true");
			foreach ($properties as $key => $value) {
				$PMVC_PROPERTIES[$key] = $value;
			}
		} else if ($prop->getName()=="property") {
			$PMVC_PROPERTIES[(string)$prop["name"]] = (string)$prop["value"];
		}
	}
	$PMVC_LOG_FILEPATH = pmvc_property_string("\${pmvc.log.path}/pmvc.log");
	$PMVC_LOG_LEVEL = pmvc_get_property("pmvc.log.level");
	if (!file_exists(pmvc_property_string("\${pmvc.log.path}"))) {
		mkdir(pmvc_property_string("\${pmvc.log.path}"), 0750, TRUE);
	}
	
	// setup classpath
	foreach ($xml->xpath("/pmvc-application/classpath/path") as $path) {
		array_push($PMVC_CLASSPATH, pmvc_property_string((string)$path));
	}
	
	// create application
	pmvc_logverbose("Creating Application object");
	$PMVC_APPLICATION = new Application($xml, $_SERVER['REQUEST_URI']);
	
	// setup request wrappers
	pmvc_logverbose("Loading RequestWrapper configuration");
	foreach ($xml->xpath("/pmvc-application/requestWrappers/requestWrapper") as $conf) {
		$objConfig 			= new ObjectConfiguration();
		$objConfig->id 		= (string)$conf["id"];
		$objConfig->class 	= (string)$conf["class"];
		foreach ($conf->xpath("property") as $prop) {
			$objConfig->config[(string)$prop["name"]] 
				= pmvc_property_string((string)$prop["value"]);
		}
		$PMVC_APPLICATION->addWrapperConfig($objConfig);
	}
	
	// setup constrollers
	pmvc_logverbose("Loading Controller configuration");
	foreach ($xml->xpath("/pmvc-application/controllers/controller") as $conf) {
		$objConfig 			= new ObjectConfiguration();
		$objConfig->id 		= (string)$conf["id"];
		$objConfig->class 	= (string)$conf["class"];
		foreach ($conf->xpath("property") as $prop) {
			$objConfig->config[(string)$prop["name"]] 
				= pmvc_property_string((string)$prop["value"]);
		}
		$PMVC_APPLICATION->addControllerConfig($objConfig);
	}
	
	// setup view handlers
	pmvc_logverbose("Loading ViewHandler configuration");
	foreach ($xml->xpath("/pmvc-application/viewHandlers/viewHandler") as $conf) {
		$objConfig 			= new ObjectConfiguration();
		$objConfig->id 		= (string)$conf["id"];
		$objConfig->class 	= (string)$conf["class"];
		foreach ($conf->xpath("property") as $prop) {
			$objConfig->config[(string)$prop["name"]] 
				= pmvc_property_string((string)$prop["value"]);
		}
		$PMVC_APPLICATION->addViewHandlerConfig($objConfig);
	}
	
	// setup data sources
	pmvc_logverbose("Loading DataSource configuration");
	foreach ($xml->xpath("/pmvc-application/datasources/datasource") as $conf) {
		$objConfig 			= new ObjectConfiguration();
		$objConfig->id 		= (string)$conf["id"];
		$objConfig->class 	= (string)$conf["class"];
		foreach ($conf->xpath("property") as $prop) {
			$objConfig->config[(string)$prop["name"]] 
				= pmvc_property_string((string)$prop["value"]);
		}
		$PMVC_APPLICATION->addDataSourceConfig($objConfig);
	}
	
	// setup request wrapper mappings
	pmvc_assimilate_mapping($xml, "/pmvc-application/requestWrapperMappings", function($conf) {
		global $PMVC_APPLICATION;
		$path = pmvc_property_string((string)$conf["path"]);
		$base = pmvc_property_string((string)$conf["base"]);
		$id = pmvc_property_string((string)$conf["to-id"]);
		$PMVC_APPLICATION->addRequestWrapperMapping($path, $id, $base);
	});
	
	// setup controller mappings
	pmvc_assimilate_mapping($xml, "/pmvc-application/controllerMappings", function($conf) {
		global $PMVC_APPLICATION;
		$path = pmvc_property_string((string)$conf["path"]);
		$base = pmvc_property_string((string)$conf["base"]);
		$id = pmvc_property_string((string)$conf["to-id"]);
		$PMVC_APPLICATION->addControllerMapping($path, $id, $base);
	});
	
	// setup view handler mappings
	pmvc_assimilate_mapping($xml, "/pmvc-application/viewHandlerMappings", function($conf) {
		global $PMVC_APPLICATION;
		$path = pmvc_property_string((string)$conf["path"]);
		$base = pmvc_property_string((string)$conf["base"]);
		$id = pmvc_property_string((string)$conf["to-id"]);
		$PMVC_APPLICATION->addViewHandlerMapping($path, $id, $base);
	});
	
	// setup 404 controller
	$fof = $xml->xpath("/pmvc-application/controllerMappings/fourOhFour");
	if ($fof && !pmvc_empty((string)$fof[0]["to-id"])) {
		$PMVC_APPLICATION->setFourOhFourController((string)$fof[0]["to-id"]);
	}

    // dispatch the request
    $PMVC_APPLICATION->dispatch();
}

function pmvc_assimilate_mapping($xml, $baseXpath, $func) {
	foreach ($xml->xpath($baseXpath."/mapping[not(@base)]") as $conf) {
		$func(Array(
			"base"	=> NULL,
			"path"	=> (string)$conf["path"],
			"to-id"	=> (string)$conf["to-id"]
		));
	}
	foreach ($xml->xpath($baseXpath."/mapping[not(not(@base))]") as $base) {
		foreach ($base->xpath("path") as $conf) {
			$func($mapping = Array(
				"base"	=> (string)$base["base"],
				"path"	=> (string)$conf["value"],
				"to-id"	=> (string)$conf["to-id"]
			));
		}
	}
}

function pmvc_get_application() {
	global $PMVC_APPLICATION;
	return $PMVC_APPLICATION;
}

function pmvc_get_request_param($name, $default=NULL) {
	return (isset($_REQUEST) && array_key_exists($name, $_REQUEST) 
		&& !pmvc_empty($_REQUEST[$name]))
		? $_REQUEST[$name] : $default;
}

/**
 * Function for loading classes.
 * 
 * @param $class the class to load
 */
function pmvc_classpath_require($file) {
	pmvc_logdebug("requiring from classpath {0}", $file);
	global $PMVC_CLASSPATH;
    foreach ($PMVC_CLASSPATH as $dir) {
    	$f = $dir.$file;
        pmvc_logverbose("checking for {0}", $f);
        if (file_exists($f)===TRUE) {
            require($f);
            return TRUE;
        }
    }
    pmvc_logfatal("Unable to require from classpath: {0}: ", $file);
    return FALSE;
}

/**
 * Function for loading classes.
 * 
 * @param $class the class to load
 */
function pmvc_loadclass($clazz) {
	pmvc_logdebug("loading {0}", $clazz);;
	global $PMVC_CLASSPATH;
    foreach ($PMVC_CLASSPATH as $dir) {
        $file = $dir."/".str_replace("\\", "/", $clazz).".class.php";
        pmvc_logverbose("checking {0} for {1}", $file, $clazz);
        if (file_exists($file)===TRUE) {
            require($file);
            return TRUE;
        }
    }
    pmvc_logfatal("Unable to import class: {0}", $clazz);
    return FALSE;
}
spl_autoload_register("pmvc_loadclass", false);

function pmvc_instantiate() {
    $args = func_get_args();
    $clazz = array_shift($args);
	$rc = new ReflectionClass($clazz);
	return (count($args)>0)
		? $rc->newInstanceArgs($args)
		: $rc->newInstance();
}

/**
 * Like pmvc_pstring except that it takes an array as
 * an argument.
 * @param $str
 * @param $args
 * @return the string
 */
function pmvc_pstringa($str, $args) {
	array_unshift($args, $str);
    return call_user_func_array("pmvc_pstring", $args);
}

/**
 * Parses a string and adds the arguments.
 * @param $str
 * @param $args
 * @return the string
 */
function pmvc_pstring() {
    $n = func_num_args();
    $a = func_get_args();
    if ($n==0) {
    	die("must pass at least 1 argument to pmvc_pstring");
    }
    $str = $a[0];
    for ($i=1; $i<$n; $i++) {
        $str = str_replace("{".($i-1)."}", $a[$i], $str);
    }
    return $str;
}

/**
 * Turns a string with property tokens into a string without
 * property tokens.
 * @param $str
 * @return unknown_type
 */
function pmvc_get_property($property) {
	global $PMVC_PROPERTIES;
	return pmvc_property_string(pmvc_pstring("\${{0}}", $property));
}

/**
 * Turns a string with property tokens into a string without
 * property tokens.
 * @param $str
 * @return unknown_type
 */
function pmvc_property_string($str) {
	global $PMVC_PROPERTIES;
	foreach ($PMVC_PROPERTIES as $key => $value) {
		$count = 0;
		$str = str_replace("\${".$key."}", $value, $str, $count);
		if ($count>0) {
			return pmvc_property_string($str);
		}		
	}
	return $str;
}

/**
 * Loads properties from the given file
 * @param $path
 * @param $dieonerror
 * @return unknown_type
 */
function pmvc_load_properties($path, $dieonerror) {
	if (!file_exists($path)) {
		if ($dieonerror!==FALSE) {
			die(pmvc_pstring("unable to load property file: {0}", $path));
		}
		return Array();
	}
	$xml = simplexml_load_file($path);
	if ($xml===FALSE) {
		die(pmvc_pstring("Unable to parse properties file: {0}", $path));
	}
	$props = Array();
	foreach ($xml->xpath("/pmvc-properties/property") as $prop) {
		$val = '';
		if (isset($prop->attributes()->value)) {
			// property value is in attribute
			$val = (string)$prop->attributes()->value;
		} else if (isset($prop->value)) {
			// property value is in child element
			$val = (string)$prop->value;
		}
		$props[(string)$prop["name"]] = $val;
	}
	return $props;
}

/**
 * Logs a message to the log file. The message can contain
 * {n} syntax with n is the index into the remaining
 * arguments passed to this function.
 * 
 * @param the log level (ERROR, WARN, INFO, DEBUG)
 * @param the message
 * @param ... any number of arguments in
 *
 */
function pmvc_logger() {
	global $PMVC_LOG_FILEPATH;
	global $PMVC_LOG_LEVEL;
	
    $n = func_num_args();
    $a = func_get_args();
    if ($n<2) {
        die("logger() called with ".$n." arguments (at least 2)");
    }
    
    // setup level index
    $l = Array("FATAL" => 1, "ERROR" => 2, "WARN" => 3, "INFO" => 4, "DEBUG" => 5, "VERBOSE" => 6);
    
    // get arguments
    $level  = array_shift($a);
    $msg    = array_shift($a);
    
    // check level
    if (!isset($l[$level]) || empty($l[$level])) {
        die(pmvc_pstring("Invalid log level: {0}", $level));
        
    // bail if the configured level isn't high enough
    } else if ($l[$level]>$l[$PMVC_LOG_LEVEL]) {
        return;
    }
    
    // format message
    $msg = pmvc_pstringa($msg, $a);
    $msg = pmvc_pstring("[{0} : {1}] {2}\n", date('h:m:s m/d/y'), $level, $msg);
    
    if ($PMVC_LOG_FILEPATH!==FALSE) {
        $f = fopen($PMVC_LOG_FILEPATH, "a+");
        fwrite($f, $msg);
        fclose($f);
    } else {
        print $msg;
    }
    
    // write to browser if in verbose mode
    if ($level=="VERBOSE" && $PMVC_LOG_FILEPATH!==FALSE) {
        print $msg;
    }
    
    // write to browser if in verbose mode
    if ($level=="FATAL" && $PMVC_LOG_FILEPATH!==FALSE) {
        die(pmvc_pstring("<b>pmvc</b>: {0}", $msg));
    }
}

/**
 * Logs an insane message to the log file. 
 * 
 * @see logger
 * @param the message
 * @param ... any number of arguments in
 *
 */
function pmvc_logverbose() {
    $args = array_reverse(func_get_args());
    array_push($args, "VERBOSE");
    call_user_func_array("pmvc_logger", array_reverse($args));
}

/**
 * Logs a debug message to the log file. 
 * 
 * @see logger
 * @param the message
 * @param ... any number of arguments in
 *
 */
function pmvc_logdebug() {
    $args = array_reverse(func_get_args());
    array_push($args, "DEBUG");
    call_user_func_array("pmvc_logger", array_reverse($args));
}

/**
 * Logs a info message to the log file. 
 * 
 * @see logger
 * @param the message
 * @param ... any number of arguments in
 *
 */
function pmvc_loginfo() {
    $args = array_reverse(func_get_args());
    array_push($args, "INFO");
    call_user_func_array("pmvc_logger", array_reverse($args));
}

/**
 * Logs a warn message to the log file. 
 * 
 * @see logger
 * @param the message
 * @param ... any number of arguments in
 *
 */
function pmvc_logwarn() {
    $args = array_reverse(func_get_args());
    array_push($args, "WARN");
    call_user_func_array("pmvc_logger", array_reverse($args));
}

/**
 * Logs a error message to the log file. 
 * 
 * @see logger
 * @param the message
 * @param ... any number of arguments in
 *
 */
function pmvc_logerror() {
    $args = array_reverse(func_get_args());
    array_push($args, "ERROR");
    call_user_func_array("pmvc_logger", array_reverse($args));
}

/**
 * Logs a fatal message to the log file. 
 * 
 * @see logger
 * @param the message
 * @param ... any number of arguments in
 *
 */
function pmvc_logfatal() {
    $args = array_reverse(func_get_args());
    array_push($args, "FATAL");
    call_user_func_array("pmvc_logger", array_reverse($args));
}

function pmvc_assert($b, $message) {
	if ($b!==TRUE) { die($message); }
}

function pmvc_empty($val) {
	return !isset($val) || empty($val) || $val=="";
}

function pmvc_matches($regex, $subject, $ignore_case=false) {
	return preg_match("\1".$regex."\1".(($ignore_case)?"i":""), $subject) > 0;
}

function pmvc_split($delimiter, $subject) {
	 return preg_split("\1".$delimiter."\1", $subject);
}

function pmvc_replace($regex, $replacement, $subject, $limit=-1, &$count=0) {
	 return preg_replace($regex,  $replacement,  $subject,  $limit, $count);
}

function pmvc_array_val($array, $key, $default=NULL) {
    return array_key_exists($key, $array) ? $array[$key] : $default;
}
?>
